package gouhuo.allbugs.top.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import gouhuo.allbugs.top.common.BusinessException;
import gouhuo.allbugs.top.common.ErrorCode;
import gouhuo.allbugs.top.mapper.TeamMapper;
import gouhuo.allbugs.top.model.domain.Team;
import gouhuo.allbugs.top.model.domain.User;
import gouhuo.allbugs.top.model.domain.UserTeam;
import gouhuo.allbugs.top.model.enums.TeamStatusEnum;
import gouhuo.allbugs.top.model.request.team.TeamJoinRequest;
import gouhuo.allbugs.top.model.request.team.TeamListRequest;
import gouhuo.allbugs.top.model.request.team.TeamQuitRequest;
import gouhuo.allbugs.top.model.request.team.TeamUpdateRequest;
import gouhuo.allbugs.top.model.vo.ChatVO;
import gouhuo.allbugs.top.model.vo.TeamUserVO;
import gouhuo.allbugs.top.model.vo.UserVO;
import gouhuo.allbugs.top.service.TeamService;
import gouhuo.allbugs.top.service.UserService;
import gouhuo.allbugs.top.service.UserTeamService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static gouhuo.allbugs.top.constant.RedisConstants.CHAT_MSG_KET;

/**
 * @author 15294780204
 * @description 针对表【team(队伍)】的数据库操作Service实现
 * @createDate 2023-03-06 18:18:51
 */
@Service
public class TeamServiceImpl extends ServiceImpl<TeamMapper, Team>
        implements TeamService {

    @Resource
    private UserTeamService userTeamService;

    @Resource
    private UserService userService;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //系统消息后缀
    long systemCount = 1;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public long addTeam(Team team, UserVO loginUser) {
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN);
        }
        long userId = loginUser.getId();
        //   1. 队伍人数 > 1 且 <= 20
        int maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0);
        if (maxNum < 1 || maxNum > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数不满足要求");
        }
        //   2. 队伍标题 <= 6
        String name = team.getName();
        if (StringUtils.isBlank(name) || name.length() < 2 || name.length() > 6) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍标题超出限制");
        }
        //   3. 描述 <= 512
        String description = team.getDescription();
        if (StringUtils.isNotBlank(description) && description.length() > 40) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍描述过长");
        }
        //   4. status 是否公开（int）不传默认为 0（公开）
        int status = Optional.ofNullable(team.getStatus()).orElse(0);
        TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
        if (statusEnum == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍状态不满足要求");
        }
        //   5. 如果 status 是加密状态，一定要有密码，且密码长度为 8~16
        String password = team.getPassword();
        if (TeamStatusEnum.SECRET.equals(statusEnum)) {
            if (StringUtils.isBlank(password) || password.length() > 16 || password.length() < 8) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码设置不正确");
            }
        }
        // 6. 超时时间 > 当前时间
        Date expireTime = team.getExpireTime();
        if (expireTime == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "过期时间不能为空");
        }
        Date now = new Date();
        if (now.after(expireTime)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "过期时间异常");
        }
        // 7. 校验用户最多创建 5 个队伍
        // todo 有 bug，可能同时创建 100 个队伍
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId", userId);
        queryWrapper.gt("expireTime", now);
        long hasTeamNum = count(queryWrapper);
        if (hasTeamNum >= 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户最多创建 5 个队伍");
        }
        // 8. 插入队伍信息到队伍表
        team.setId(null);
        team.setUserId(userId);
        boolean result = save(team);
        Long teamId = team.getId();
        if (!result || teamId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败");
        }
        // 9. 插入用户  => 队伍关系到关系表
        UserTeam userTeam = new UserTeam();
        userTeam.setUserId(userId);
        userTeam.setTeamId(teamId);
        userTeam.setTies(2);
        result = userTeamService.save(userTeam);
        if (!result) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败");
        }
        toCreaterMsg(userId, teamId, 0, name, name);
        return teamId;
    }

    @Override
    public boolean updateTeam(TeamUpdateRequest teamUpdateRequest, UserVO loginUser) {
        Long id = teamUpdateRequest.getId();
        if (id == null || id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team oldTeam = list(new QueryWrapper<Team>().select("userId").eq("id", id)).get(0);
        if (oldTeam == null) throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在");
        // 只有管理员或者队伍的创建者可以修改
        if (!Objects.equals(oldTeam.getUserId(), loginUser.getId()) && !userService.isAdmin(loginUser)) {
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(teamUpdateRequest.getStatus());
        if (statusEnum.equals(TeamStatusEnum.SECRET)) {
            if (StringUtils.isBlank(teamUpdateRequest.getPassword())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "加密房间必须要设置密码");
            }
        }
        Team updateTeam = new Team();
        BeanUtils.copyProperties(teamUpdateRequest, updateTeam);
        return updateById(updateTeam);
    }

    @Override
    public boolean joinTeam(TeamJoinRequest teamJoinRequest, UserVO loginUser) {
        Long teamId = teamJoinRequest.getTeamId();
        Team team = getById(teamId);
        Date expireTime = team.getExpireTime();
        Date now = new Date();
        if (expireTime != null && expireTime.before(now)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已过期");
        }
        Integer status = team.getStatus();
        TeamStatusEnum teamStatusEnum = TeamStatusEnum.getEnumByValue(status);
        if (TeamStatusEnum.PRIVATE.equals(teamStatusEnum)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "禁止加入私有队伍");
        }
        String password = teamJoinRequest.getPassword();
        if (TeamStatusEnum.SECRET.equals(teamStatusEnum)) {
            if (StringUtils.isBlank(password) || !password.equals(team.getPassword())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
            }
        }
        // 该用户已加入的队伍数量
        long userId = loginUser.getId();
        // 只有一个线程能获取到锁
        RLock lock = redissonClient.getLock("gouhuo:join_team");
        try {
            // 抢到锁并执行
            while (true) {
                if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
                    //获取用户已加入的队伍
                    QueryWrapper<UserTeam> userTeamRequestWrapper = new QueryWrapper<>();
                    userTeamRequestWrapper.eq("userId", userId).eq("ties", 1);
                    List<UserTeam> list = userTeamService.list(userTeamRequestWrapper);
                    //统计有效队伍的数量
                    if (!list.isEmpty()) {
                        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
                        List<Long> ids = list.stream().map(UserTeam::getTeamId).collect(Collectors.toList());
                        queryWrapper.in("id", ids).gt("expireTime", now);
                        long hasJoinNum = count(queryWrapper);
                        if (hasJoinNum > 5) throw new BusinessException(ErrorCode.PARAMS_ERROR, "最多加入 5 个队伍");
                    }
                    // 已加入队伍的人数
                    long teamHasJoinNum = countTeamUserByTeamId(teamId);
                    if (teamHasJoinNum >= team.getMaxNum())
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数已满");
                    // 不能重复加入已加入的队伍
                    userTeamRequestWrapper = new QueryWrapper<>();
                    userTeamRequestWrapper.eq("userId", userId);
                    userTeamRequestWrapper.eq("teamId", teamId);
                    // 之前是否有过加入记录，没有记录就新插入一条，已有记录只需改变ties值即可，
                    UserTeam userTeam = userTeamService.getOne(userTeamRequestWrapper);
                    if (userTeam == null) {
                        userTeam = new UserTeam();
                        userTeam.setUserId(userId);
                        userTeam.setTeamId(teamId);
                    } else {
                        userTeamRequestWrapper.eq("ties", 1);
                        long hasUserJoinTeam = userTeamService.count(userTeamRequestWrapper);
                        if (hasUserJoinTeam > 0) throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户已加入该队伍");
                    }
                    userTeam.setTies(1);
                    boolean update = userTeamService.saveOrUpdate(userTeam);
                    if (update) toCreaterMsg(team.getUserId(), teamId, 2, loginUser.getUsername(), team.getName());
                    return update;
                }
            }
        } catch (InterruptedException e) {
            log.error("doCacheRecommendUser error", e);
            return false;
        } finally {
            // 只能释放自己的锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean quitTeam(TeamQuitRequest teamQuitRequest, UserVO loginUser) {
        Long teamId = teamQuitRequest.getTeamId();
        List<Team> list = list(new QueryWrapper<Team>().select("userId", "name").eq("id", teamId));
        Long createrId = list.get(0).getUserId();
        String name = list.get(0).getName();
        long userId = loginUser.getId();
        //获取已加入的队伍
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId", userId);
        queryWrapper.eq("teamId", teamId);
        queryWrapper.eq("ties", 1);
        long count = userTeamService.count((queryWrapper));
        if (count == 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "未加入队伍");
        }
        long teamHasJoinNum = countTeamUserByTeamId(teamId);
        // todo 队伍只剩一人:测试。。。此时最后一个时，应该执行解散队伍的操作
        if (teamHasJoinNum == 1) {
            removeById(teamId);
        } else {
            // 队伍还剩至少两人并且目前用户是队长
            if (createrId == userId) {
                // 目前没有队长退队的逻辑，只能解散队伍,并把队伍转移给最早加入的用户
                // 1. 查询已加入队伍的所有用户和加入时间
                QueryWrapper<UserTeam> userTeamRequestWrapper = new QueryWrapper<>();
                userTeamRequestWrapper.eq("teamId", teamId);
                userTeamRequestWrapper.last("order by id asc limit 2");
                List<UserTeam> userTeamList = userTeamService.list(userTeamRequestWrapper);
                if (CollectionUtils.isEmpty(userTeamList) || userTeamList.size() <= 1) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR);
                }
                UserTeam nextUserTeam = userTeamList.get(1);
                //更新用户队伍关系表中的ties字段
                nextUserTeam.setTies(2);
                userTeamService.updateById(nextUserTeam);
                Long nextTeamLeaderId = nextUserTeam.getUserId();
                // 更新当前队伍的队长
                Team updateTeam = new Team();
                updateTeam.setId(teamId);
                updateTeam.setUserId(nextTeamLeaderId);
                boolean result = updateById(updateTeam);
                if (!result) throw new BusinessException(ErrorCode.SYSTEM_ERROR, "移交队长失败，无法解散");
            }
        }
        UserTeam userTeam = userTeamService.getOne(queryWrapper);
        // 移除关系
        userTeam.setTies(0);
        boolean update = userTeamService.updateById(userTeam);
        if (update) toCreaterMsg(createrId, teamId, 3, loginUser.getUsername(), name);
        return update;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteTeam(long id, UserVO loginUser) {
        // 校验队伍是否存在
        Team team = getById(id);
        if (team == null) return false;
        long teamId = team.getId();
        Long userId = loginUser.getId();
        String name = team.getName();
        // 校验是否为队伍的队长
        if (!Objects.equals(team.getUserId(), userId)) throw new BusinessException(ErrorCode.NO_AUTH, "无访问权限");
        // 移除所有加入队伍的关联信息
        QueryWrapper<UserTeam> userTeamRequestWrapper = new QueryWrapper<>();
        //给所有成员发送队伍解散提醒
        userTeamRequestWrapper.eq("teamId", teamId).ne("ties", 0).select("userId");
        userTeamService.list(userTeamRequestWrapper).forEach(userTeam -> {
            Long userOrCreaterId = userTeam.getUserId();
            toCreaterMsg(userOrCreaterId, teamId, 1, name, name);
        });
        boolean result = userTeamService.remove(new QueryWrapper<UserTeam>().eq("teamId", teamId));
        if (!result) throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除队伍关联人员失败");
        return removeById(teamId);
    }

    /**
     * 获取队伍当前人数
     *
     * @param teamId
     * @return
     */
    private long countTeamUserByTeamId(long teamId) {
        QueryWrapper<UserTeam> userTeamRequestWrapper = new QueryWrapper<>();
        userTeamRequestWrapper.eq("teamId", teamId).ne("ties", 0);
        return userTeamService.count(userTeamRequestWrapper);
    }

    /**
     * 返回已经加入队伍的人数
     *
     * @param userTeamList
     * @param teamList
     * @param loop
     */
    public void joinUserNum(List<UserTeam> userTeamList, List<TeamUserVO> teamList, boolean loop) {
        Map<Long, List<UserTeam>> listMap = userTeamList.stream()
                .collect(Collectors.groupingBy(UserTeam::getTeamId));
        teamList.forEach(team -> {
            team.setHasJoinNum(listMap.getOrDefault(team.getId(), new ArrayList<>()).size());
            if (loop) team.setTies(1);
        });
    }

    @Override
    public List<TeamUserVO> listTeams(TeamListRequest teamListRequest, boolean isAdmin, List<Long> notShow) {
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        // 组合查询条件
        if (teamListRequest != null) {
            Long id = teamListRequest.getId();
            if (id != null && id > 0) {
                queryWrapper.eq("id", id);
            }
            List<Long> idList = teamListRequest.getIdList();
            if (CollectionUtils.isNotEmpty(idList)) {
                queryWrapper.in("id", idList);
            }
            String searchText = teamListRequest.getSearchText();
            if (StringUtils.isNotBlank(searchText)) {
                queryWrapper.and(qw -> qw.like("name", searchText).or().like("description", searchText));
            }
            String name = teamListRequest.getName();
            if (StringUtils.isNotBlank(name)) {
                queryWrapper.like("name", name);
            }
            String description = teamListRequest.getDescription();
            if (StringUtils.isNotBlank(description)) {
                queryWrapper.like("description", description);
            }
            Integer maxNum = teamListRequest.getMaxNum();
            // 查询最大人数相等的
            if (maxNum != null && maxNum > 0) {
                queryWrapper.eq("maxNum", maxNum);
            }
            Long userId = teamListRequest.getUserId();
            // 根据创建人来查询
            if (userId != null && userId > 0) {
                queryWrapper.eq("userId", userId);
            }
            // 根据状态来查询
            Integer status = teamListRequest.getStatus();
            TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
            if (statusEnum != null) {
                if (!isAdmin && statusEnum.equals(TeamStatusEnum.PRIVATE)) {
                    throw new BusinessException(ErrorCode.NO_AUTH);
                }
                queryWrapper.eq("status", statusEnum.getValue());
            }
        }
        // 不展示已过期的队伍 expireTime is null or expireTime > now()
        queryWrapper.and(qw -> qw.gt("expireTime", new Date()).or().isNull("expireTime"));
        //不展示自己创建的和已加入的队伍
        if (notShow != null && notShow.size() != 0) {
            queryWrapper.notIn("id", notShow);
        }
        List<Team> teamList = list(queryWrapper);
        List<TeamUserVO> teamUserVOList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(teamList)) {
            // 关联查询创建人的用户信息
            for (Team team : teamList) {
                Long userId = team.getUserId();
                if (userId == null) continue;
                User user = userService.getById(userId);
                TeamUserVO teamUserVO = new TeamUserVO();
                BeanUtils.copyProperties(team, teamUserVO);
                // 脱敏用户信息
                if (user != null) {
                    UserVO userVO = new UserVO();
                    BeanUtils.copyProperties(user, userVO);
                    teamUserVO.setCreateUser(userVO);
                }
                teamUserVOList.add(teamUserVO);
            }
        }
        return teamUserVOList;
    }

    @Override
    @Transactional
    public List<TeamUserVO> listTeamsByType(UserVO loginUser, Long userId, int type) {
        //查询与用户有关的队伍信息
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId", userId).eq("ties", type);
        List<UserTeam> userTeamList = userTeamService.list(queryWrapper);
        if (userTeamList.size() == 0) return new ArrayList<>();
        //收集这些队伍的队伍id
        List<Long> idList = userTeamList.stream().map(UserTeam::getTeamId).collect(Collectors.toList());
        //查询加入这些队伍的所有人的相关信息
        userTeamList = userTeamService.list(new LambdaQueryWrapper<UserTeam>().in(UserTeam::getTeamId, idList));
        //排除过期的队伍
        QueryWrapper<Team> teamRequestWrapper = new QueryWrapper<>();
        teamRequestWrapper.and(qw -> qw.gt("expireTime", new Date()).or().isNull("expireTime"));
        teamRequestWrapper.in("id", idList);
        List<Team> teamList = list(teamRequestWrapper);
        if (teamList.size() < idList.size()) {
            //给所有相关人员发送提醒并解散队伍
            List<Long> teamIdList = teamList.stream().map(Team::getId).collect(Collectors.toList());
            List<Long> expireTeamIdList = idList.stream()
                    .filter(id -> !teamIdList.contains(id)).collect(Collectors.toList());
            for (Long teamId : expireTeamIdList) {
                List<Team> list = list(new QueryWrapper<Team>().select("name").eq("id", teamId));
                String name = list.get(0).getName();
                // 移除所有加入队伍的关联信息
                QueryWrapper<UserTeam> userTeamRequestWrapper = new QueryWrapper<>();
                userTeamRequestWrapper.eq("teamId", teamId).ne("ties", 0).select("userId");
                userTeamService.list(userTeamRequestWrapper).forEach(userTeam -> {
                    Long userOrCreaterId = userTeam.getUserId();
                    //给所有成员发送队伍过期和解散解散提醒
                    toCreaterMsg(userOrCreaterId, teamId, 4, name, name);
                    toCreaterMsg(userOrCreaterId, teamId, 1, name, name);
                });
                boolean result = userTeamService.remove(new QueryWrapper<UserTeam>().eq("teamId", teamId));
                if (!result) throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除队伍关联人员失败");
                removeById(teamId);
            }
        }
        //如果访问的是别人的队伍信息，查询当前用户加入的队伍并收集这些队伍的id
        List<Long> curIdList = null;
        if (!userId.equals(loginUser.getId())) {
            QueryWrapper<UserTeam> wrapper = new QueryWrapper<>();
            wrapper.eq("userId", loginUser.getId()).eq("ties", 1);
            List<UserTeam> list = userTeamService.list(wrapper);
            if (list.size() != 0) {
                List<Team> curUserJoin = listByIds(list.stream().map(UserTeam::getTeamId).collect(Collectors.toList()));
                curIdList = curUserJoin.stream().map(Team::getId).collect(Collectors.toList());
            }
        }
        List<TeamUserVO> teamUserVOList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(teamList)) {
            for (Team team : teamList) {
                // 查询并关联创建人的信息
                TeamUserVO teamUserVO = new TeamUserVO();
                BeanUtils.copyProperties(team, teamUserVO);
                User user = userService.getById(userId);
                if (user != null) {
                    UserVO userVO = new UserVO();
                    BeanUtils.copyProperties(user, userVO);
                    teamUserVO.setCreateUser(userVO);
                }
                if (curIdList != null) {
                    //当前访问的别人的队伍信息
                    boolean loop = true;
                    for (Long id : curIdList) {
                        //如果自己和对方加入了相同的队伍，ties设为1，否者设为0
                        if (team.getId().equals(id)) {
                            teamUserVO.setTies(1);
                            loop = false;
                        }
                        if (loop) teamUserVO.setTies(0);
                    }
                }
                teamUserVOList.add(teamUserVO);
            }
        }
        joinUserNum(userTeamList, teamUserVOList, type == 1 && userId.equals(loginUser.getId()));
        return teamUserVOList;
    }

    /**
     * 向队伍关联人员发送相关信息
     *
     * @param createrId
     * @param teamId
     * @param type
     * @param joinerName
     * @param teamName
     */
    public void toCreaterMsg(Long createrId, Long teamId, Integer type, String joinerName, String teamName) {
        String hashKey = "system" + systemCount++;
        String k = CHAT_MSG_KET + createrId + ":msg";
        ChatVO chatVO = new ChatVO();
        chatVO.setToId(createrId);
        chatVO.setFromName("队伍相关-" + teamName);
        chatVO.setFromUrl("http://cdn.allbugs.top/kun.png");
        if (teamId != null) {
            chatVO.setTeamId(teamId);
        }
        chatVO.setScope(3);
        chatVO.setCreateTime(String.valueOf(System.currentTimeMillis()));
        chatVO.setSysStatus(hashKey);
        chatVO.setType(type);
        switch (type) {
            case 0:
                chatVO.setText("队伍" + joinerName + "刚刚创建成功");
                break;
            case 1:
                chatVO.setText("队伍" + joinerName + "刚刚解散了");
                break;
            case 2:
                chatVO.setText("用户" + joinerName + "刚刚加入了队伍");
                break;
            case 3:
                chatVO.setText("用户" + joinerName + "刚刚退出了队伍");
                break;
            case 4:
                chatVO.setText("队伍" + joinerName + "刚刚过期了");
                break;
        }
        stringRedisTemplate.opsForHash().put(k, hashKey, new Gson().toJson(chatVO));
        String statusKey = CHAT_MSG_KET + createrId + ":status";
        stringRedisTemplate.opsForSet().add(statusKey, hashKey);
    }

}
